----------------------------------------------------------------------------------
-- Company:        RIT
-- Engineer:       Sam Skalicky
-- 
-- Create Date:    17:49:05 12/11/2009 
-- Design Name:    MSD P10662
-- Module Name:    Device - Behavioral 
-- Project Name:   Inout
-- Target Devices: Spartan 6 LXT
-- Tool versions:  QuestaSim-64 6.4c
-- Description:    This a bi-directional I2C/Two-wire port implementation in VHDL
--
-- Notes: Data is the bi-directional port, Clk is typeset as an inout in the idea that
-- the clock could be generated to this port (Master device) or recieved from this port
-- in the case of a Slave device. However it is not meant to be a bi-directional port
--
-- Dependencies:   None
--
-- Revision: 1.0
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

Library UNISIM;
use UNISIM.vcomponents.all;

entity I2Cmaster is
    Generic (register_data_width : natural;
             register_id_width : natural);
    Port ( PS2_Data : inout  STD_LOGIC;
           PS2_Clk : in  STD_LOGIC;
           FPGAClk : in std_logic;
			  I2C_Clk_out : out std_logic;
           Send : in std_logic;
           Instruction : in std_logic_vector(7 downto 0);
           register_id : in std_logic_vector(register_id_width-1 downto 0);
           Data : in std_logic_vector(register_data_width-1 downto 0));
end I2Cmaster;

architecture Behavioral of I2Cmaster is

	signal Dir,out_i2c,in_i2c,out_i2cclk : std_logic:='1';
	type states is (idle,start,sendAddress,sendReg,sendData,recieve,stop,ack,recAck);
  signal pstate,nstate : states:=idle;
  signal counter : std_logic_vector(4 downto 0):=(others=>'0');
  signal shiftReg,i2cAddr : std_logic_vector(7 downto 0):="00000000";
  signal rID : std_logic_vector(register_id_width-1 downto 0):=(others=>'0');
  signal sData : std_logic_vector(register_data_width-1 downto 0):=(others=>'0');
  signal writeCount : natural:=0;
  signal ack_count : std_logic_vector(11 downto 0):=(others=>'0');
  signal delay_count : std_logic_vector(15 downto 0):=(others=>'0');
  signal ClkEdge : std_logic_vector(1 downto 0):="00";
  signal ClkFallingEdge,ClkRisingEdge,prevClk,sSend : std_logic:='0';
  
begin
  
  sData<=Data;
  sSend<=Send;
  --i2cAddr<=Instruction;
  rID<=register_id;
  I2C_Clk_out <= out_i2cclk;
  
	--IOBUF: Single-ended Bi-directional Buffer All devices
	--Xilinx HDL Libraries Guide version 8.1i
	IOBUF_inst : IOBUF 
		generic map (
			DRIVE => 12, 
			IBUF_DELAY_VALUE => "0", 	-- Specify the amount of added input delay for buffer, "0"-"16" (Spartan-3E only)
			IFD_DELAY_VALUE => "AUTO", -- Specify the amount of added delay for input register, "AUTO", "0"-"8" (Spartan-3E only)
			IOSTANDARD => "DEFAULT",
			SLEW => "SLOW") 
		port map (
			O => out_i2c, 		-- Buffer output
			IO => PS2_Data, 	-- Buffer inout port (connect directly to top-level port)
			I => in_i2c, 		-- Buffer input
			T => Dir		-- 3-state enable input
		); 
	--End of IOBUF_inst instantiation

  ------------------------------
  -- I2C Clk edge detector
  ------------------------------
  process(PS2_Clk,FPGAClk)
  begin
    if rising_edge(FPGAClk) then
	   ClkEdge<=ClkEdge(0) & PS2_Clk;
		
		if ClkEdge = "00" and prevClk = '1' then
		  ClkFallingEdge <= '1';
		  ClkRisingEdge<='0';
		  prevClk<='0';
		elsif ClkEdge = "11" and prevClk = '0' then
		  prevClk<='1';
		  ClkFallingEdge<='0';
		  ClkRisingEdge<='1';
		else
		  ClkFallingEdge<='0';
		  ClkRisingEdge<='0';
		end if;
	 end if;
  end process;

  ------------------------------
  -- Control
  ------------------------------
  process (FPGAClk,PS2_Clk,PS2_Data,nstate)
  begin
    if rising_edge(FPGAClk) then
      
    --state machine
	   case nstate is
		   when idle =>	--state changes when start bit received
		     if sSend = '1' then
			     if PS2_Clk='0' then
				     nstate<=start;
				     pstate<=nstate;
				   end if;
			   end if;
			 when start =>
			   if ack_count > X"0ED" then
 		       nstate<=sendAddress;
 		       pstate<=nstate;
 		     end if;
			 when sendAddress =>
			   if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
 		   when sendReg =>
 		     if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
 		   when sendData =>
 		     if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
		   when recieve =>		--receive 8 bits
		       if counter = "01000" then
		       nstate<=ack;
		       pstate<=nstate;
		       end if;
		   when ack =>
		     if ack_count = X"1DF" then
      		       nstate<=stop;
      		       pstate<=nstate;
		     end if;
		   when recAck =>
		     if delay_count > X"0163" then
		       if pstate = sendAddress then
		         nstate<=sendReg;
		         pstate<=nstate;
		       elsif pstate = sendReg then
		         if i2cAddr(0) = '0' then
		           nstate<=recieve;
		           pstate<=nstate;
		         else 
		           nstate<=sendData;
		           pstate<=nstate;
		         end if;
		       else
		         nstate<=stop;
		         pstate<=nstate;
		       end if;
		     end if;
		 when stop =>
		   if ack_count = X"079" then
		     nstate<=idle;
		     pstate<=nstate;
		   end if;
	   end case;
	  end if;                         
  end process;
  ------------------------------
  
  
  ------------
  --Datapath
  ------------
  process (FPGAClk,nstate,PS2_Clk,counter,shiftReg)
    
  begin
   if rising_edge(FPGAClk) then
    case nstate is
       when idle =>
			  out_i2cclk<='1';
   	     counter<="00000";
   	     shiftReg<=i2cAddr;
	     when start =>
	       if ClkRisingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     
		       case ack_count is
		         when X"000" =>
		           Dir<='0';
        		     in_i2c<='1';
		         when X"030" =>
		           out_i2cclk<=PS2_Clk;
		           in_i2c<='0';
		           ack_count<=ack_count + 1;
		         when X"0DE" =>
		           out_i2cclk<=PS2_Clk;
        					  in_i2c<='1';
		           Dir<='1';
		           in_i2c<='Z';
		           ack_count<=ack_count+1;
		         --when X"1E0" =>
		         --  ack_count<=X"000";
		         when others =>
		           if ack_count > X"030" then
    		           out_i2cclk<=PS2_Clk;
 		           end if;
		           ack_count<= ack_count + 1;  
		       end case;
	     when sendAddress =>
	       out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
    		       delay_count<=X"0001";
    		       writeCount<=writeCount + 1;
    		       in_i2c<=shiftReg(0);
    		       shiftReg<='0'&shiftReg(7 downto 1);
 		     end if;		
		     
		       case delay_count is
		         when X"0000" => 
		           Dir<='0';
		           if writeCount = 0 then
		             in_i2c<=shiftReg(0);
    		           writeCount<=writeCount + 1;
		             shiftReg<='0'&shiftReg(7 downto 1);
		           end if;
		         when X"0020" =>
		           Dir<='0';
		           delay_count<=delay_count + 1;
		         when X"06D3" =>
					  in_i2c<='1';
		           Dir<='1';
		           delay_count<=delay_count + 1;
		           shiftReg<=rID;
		         when X"06D4" =>
		           delay_count<=X"0000";
		         when others =>
		           delay_count<=delay_count + 1;
		       end case;
		   when sendReg =>
		     out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
    		       delay_count<=X"0001";
    		       writeCount<=writeCount + 1;
    		       in_i2c<=shiftReg(0);
    		       shiftReg<='0'&shiftReg(7 downto 1);
 		     end if;		
		     
		       case delay_count is
		         when X"0000" => 
		           Dir<='0';
		           if writeCount = 0 then
		             in_i2c<=shiftReg(0);
    		           writeCount<=writeCount + 1;
		             shiftReg<='0'&shiftReg(7 downto 1);
		           end if;
		         when X"0020" =>
		           Dir<='0';
		           delay_count<=delay_count + 1;
		         when X"06D3" =>
					  in_i2c<='1';
		           Dir<='1';
		           delay_count<=delay_count + 1;
		           shiftReg<=sData;
		         when X"06D4" =>
		           delay_count<=X"0000";
		         when others =>
		           delay_count<=delay_count + 1;
		       end case;
		   when sendData =>
		     out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
    		       delay_count<=X"0001";
    		       writeCount<=writeCount + 1;
    		       in_i2c<=shiftReg(0);
    		       shiftReg<='0'&shiftReg(7 downto 1);
 		     end if;		
		     
		       case delay_count is
		         when X"0000" => 
		           Dir<='0';
		           if writeCount = 0 then
		             in_i2c<=shiftReg(0);
    		           writeCount<=writeCount + 1;
		             shiftReg<='0'&shiftReg(7 downto 1);
		           end if;
		         when X"0020" =>
		           Dir<='0';
		           delay_count<=delay_count + 1;
		         when X"06D3" =>
					  in_i2c<='1';
		           Dir<='1';
		           delay_count<=delay_count + 1;
		         when X"06D4" =>
		           delay_count<=X"0000";
		         when others =>
		           delay_count<=delay_count + 1;
		       end case;
		   when recieve =>		--receive 8 bits
		     out_i2cclk<=PS2_Clk;
		     ack_count<=X"000";
		     if ClkRisingEdge ='1' then
		       counter<=counter + 1;
		       shiftReg<=shiftReg(6 downto 0) & out_i2c;
		     end if;
		   when ack =>
		     out_i2cclk<=PS2_Clk;
		     if ClkFallingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     if counter = "10000" then
		     counter <= "10001";
		     end if;
		       case ack_count is
		         when X"000" => null;
		         when X"002" =>
		           Dir<='0';
		           in_i2c<='0';
		           ack_count<=ack_count + 1;
		         when X"1DF" =>
					  in_i2c<='1';
		           Dir<='1';
		           in_i2c<='Z';
		           ack_count<=X"000";
		         when X"1E0" =>
		           ack_count<=X"000";
		         when others =>
		           ack_count<= ack_count + 1;  
		       end case;
		   when recAck =>
		     out_i2cclk<=PS2_Clk;
		     writeCount<=0;
		     if delay_count < X"0164" then
           delay_count<=delay_count + 1;
         else
           delay_count<=X"0000";
         end if;
       when stop =>
         out_i2cclk<=PS2_Clk;
         if ClkRisingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     
		       case ack_count is
		         when X"000" => 
		           Dir<='0';
		           in_i2c<='0';
		           out_i2cclk<=PS2_Clk;
		         when X"002" =>
		           Dir<='0';
		           in_i2c<='0';
		           ack_count<=ack_count + 1;
		           out_i2cclk<=PS2_Clk;
		         when X"040" =>
		           in_i2c<='1';
		           ack_count<=ack_count + 1;
		           out_i2cclk<=PS2_Clk;
		         when X"078" =>
					     in_i2c<='1';
		           Dir<='1';
		           in_i2c<='Z';
		           ack_count<=ack_count+1;
		           out_i2cclk<='1';
		         when X"079" =>
		           ack_count<=X"000";
		           out_i2cclk<='1';
		         when others =>
		           ack_count<= ack_count + 1;  
		           out_i2cclk<=PS2_Clk;
		       end case;
	   end case;
	  end if; 
  end process;
end Behavioral;